/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"encoding/json"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/lease"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"strings"
	"sync"

	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	errorScopePrefix           = "Scope error: "
	infoScopesOperationPostfix = " operation for a custom \"kind: Scope\" configuration"
)

// ScopeReconciler reconciles a Scope object
type ScopeReconciler struct {
	client.Client
	Scheme *runtime.Scheme

	once            sync.Once
	scopeObj        v1alpha1.Scope
	deletedScopeObj v1alpha1.Scope
	initedFlag      bool
}

func (r *ScopeReconciler) deferLog(req ctrl.Request, configData []byte, err error) {
	var status v1alpha1.LogStatus
	var inf string
	var level zapcore.Level

	if err == nil {
		inf = string(eventOperation) + " operation for a custom \"kind: Scope\" configuration"
		status = v1alpha1.SuccessStatus
		level = 1
	} else {
		inf = errorScopePrefix + err.Error()
		status = v1alpha1.FailedStatus
		level = zap.ErrorLevel
	}
	logs.PersistLog(level,
		"rqUID", getRqUID(clusterName, req.Namespace, scopeType, req.Name),
		"message", string(configData),
		"information", inf,
		"status", status,
		"operationName", eventOperation,
		"serviceReceiver", serviceReceiver)
}

func (r *ScopeReconciler) DeleteEventHndl(req ctrl.Request) (err error) {

	confByte, _ := json.Marshal(r.deletedScopeObj.Spec)
	r.deferLog(req, confByte, err)
	return nil
}

func (r *ScopeReconciler) AddOrUpdateEventHndl(ctx context.Context, req ctrl.Request) (err error) {
	confByte, _ := json.Marshal(r.scopeObj.Spec)

	r.deferLog(req, confByte, err)

	return nil
}

//+kubebuilder:rbac:groups=kubelatte.io,resources=scopes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubelatte.io,resources=scopes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kubelatte.io,resources=scopes/finalizers,verbs=update

func (r *ScopeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	Mux.Lock()
	defer Mux.Unlock()

	r.once.Do(func() {
		r.initedFlag = true
	})

	// Check delete event
	err := r.Get(ctx, req.NamespacedName, &r.scopeObj)
	if err != nil {
		if errors.IsNotFound(err) {
			if err = r.DeleteEventHndl(req); err != nil {
				return ctrl.Result{}, err
			}
		}
		return ctrl.Result{}, err
	}

	// Add or update event
	err = r.AddOrUpdateEventHndl(ctx, req)
	if err != nil {
		return ctrl.Result{}, err
	}

	return r.updateStatus(ctx, r.scopeObj, false)
}

func (r *ScopeReconciler) updateStatus(ctx context.Context, scope v1alpha1.Scope, isAudit bool) (ctrl.Result, error) {
	if lease.AmILeader() {
		data, _ := json.Marshal(scope.Spec)
		if CheckHashInStatus(scope.Status.Conditions) {
			for i, condition := range scope.Status.Conditions {
				if condition.Type == v1alpha1.LastHandledHash {
					sha1Hash := GetHash(data)
					if isAudit && strings.Compare(scope.Status.Conditions[i].Message, sha1Hash) != 0 {
						logs.Debugf("Scope status checker: version compare failed: %s != %s", scope.Status.Conditions[i].Message, sha1Hash)
					}
					scope.Status.Conditions[i].Message = sha1Hash
					scope.Status.Conditions[i].LastTransitionTime = v1.Now()
				}
			}
		} else {
			scope.Status.Conditions = append(scope.Status.Conditions, AddHashCondition(data))
		}
		err := r.Status().Update(ctx, &scope, &client.SubResourceUpdateOptions{})
		if err != nil {
			logs.Error("Scope controller: failed update status crd: " + err.Error())
			return ctrl.Result{}, err
		}
	}
	return ctrl.Result{}, nil
}

func (r *ScopeReconciler) manageEvents() predicate.Predicate {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			eventOperation = addEvent
			obj := *e.Object.(*v1alpha1.Scope)
			if IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, NSSelectorCommon) {
				return false
			}
			util.SendEvent(StorageUpdateScopeEvent, obj)
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			eventOperation = updateEvent

			if e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() && e.ObjectNew.GetName() != LeaseLockName {
				obj := *e.ObjectNew.(*v1alpha1.Scope)
				if IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, NSSelectorCommon) {
					return false
				}
				util.SendEvent(StorageUpdateScopeEvent, obj)
				return true
			}

			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			eventOperation = deleteEvent
			r.deletedScopeObj = *e.Object.(*v1alpha1.Scope)
			if IsOperatorInClusterMode() && !CheckNamespace(r.deletedScopeObj.Namespace, NSSelectorCommon) {
				return false
			}
			util.SendEvent(StorageDeleteScopeEvent, r.deletedScopeObj)
			return true
		},
	}
}

// SetupWithManager sets up the controller with the Manager.
func (r *ScopeReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&v1alpha1.Scope{}).
		WithEventFilter(r.manageEvents()).
		Complete(r)
}

func (r *ScopeReconciler) HealthCheck() bool {
	return r.initedFlag
}
